
namespace ItProject.Andir.Cryptography
{
	/// <summary>
	/// Represents the base class from which all implementation of block hash algorithms must derive.
	/// </summary>
	public abstract class BlockHashAlgorithm
		: System.Security.Cryptography.HashAlgorithm
	{
		private IBlockHashAlghorithmImpl _impl;

		private byte[] _endBlock;
		private int _endBlockLength;

		/// <summary>
		/// Constructor, get intstance of type which implementating IBlockHashAlgorithm.
		/// </summary>
		/// <param name="impl"></param>
		public BlockHashAlgorithm(IBlockHashAlghorithmImpl impl)
		{
			_impl = impl;

			_endBlock = new byte[_impl.BlockSize];
			_endBlockLength = 0;
		}

		/// <summary>
		/// Initialization algorithm variables.
		/// </summary>
		public override void Initialize()
		{
			_endBlockLength = 0;

			_impl.Initialize();
		}
		
		/// <summary>
		/// Main hash procedure.
		/// </summary>
		/// <param name="array">byte array</param>
		/// <param name="offset">offset in array</param>
		/// <param name="length">length of block for processing</param>
		protected override void HashCore(byte[] array, int offset, int length)
		{
			int currentOffset = offset, currentLength = length;

			if(_endBlockLength > 0)
			{
				System.Buffer.BlockCopy(
					array, currentOffset,
					_endBlock, _endBlockLength,
					_endBlock.Length - _endBlockLength
					);

				_impl.ProcessBlock(_endBlock, 0);

				currentOffset += _endBlockLength;
				currentLength -= _endBlockLength;

				_endBlockLength = 0;
			}

			int blockCount = currentLength / _impl.BlockSize;
			_endBlockLength = currentLength % _impl.BlockSize;

			for(int ii = 0; ii < blockCount; ii++, currentOffset += _impl.BlockSize)
				_impl.ProcessBlock(array, currentOffset);

			if(_endBlockLength != 0)
				System.Buffer.BlockCopy(
					array, currentOffset,
					_endBlock, 0,
					_endBlockLength
					);
		}

		/// <summary>
		/// Hash final block.
		/// </summary>
		/// <returns>hash value</returns>
		protected override byte[] HashFinal()
		{
			_impl.ProcessFinalBlock(_endBlock, 0, _endBlockLength);

			return _impl.Result;
		}
	}
}
